package push_client

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/go-ini/ini"
	"github.com/levigross/grequests"

	"gitee.com/goframe/lt-collector/common"
	"gitee.com/goframe/lt-collector/libs/abio"
	"gitee.com/goframe/lt-collector/libs/libgin"
	"gitee.com/goframe/lt-collector/libs/utils"
)

var (
	packName = "install.zip"
)

func NewConfig() *Config {
	c := &Config{
		ServerDomain: "127.0.0.1:39001",
		SoftDomain:   "127.0.0.1:39200",
		CallBackURI:  "",
		PackFiles:    []string{"ty-collector", "install.sh"},
		Soft: Soft{
			Name:        "ty-collector",
			NameCN:      "采集系统",
			Branch:      "dev",
			SoftVersion: "0.0.1",
		},
	}
	var err error
	c.once.Do(func() {
		c.configFileName = filepath.Join(abio.SelfDir(), "conf", "push_client.ini")
		c.iniFile, err = ini.Load(c.configFileName)
		if err == nil {
			err = c.iniFile.MapTo(c)
			return
		}
		c.iniFile = ini.Empty()
		if err = c.iniFile.ReflectFrom(c); err != nil {
			return
		}
		err = c.iniFile.SaveTo(c.configFileName)
	})
	if err != nil {
		log.Fatalln(err)
	}
	return c
}

func PushAction() (err error) {
	pushClient := new(Push)
	if err = pushClient.checkHash(); err != nil {
		return
	}
	if !pushClient.needPush {
		return errors.New("不需要push")
	}
	if err = pushClient.pack(); err != nil {
		return
	}
	if err = pushClient.postUpload(); err != nil {
		return
	}
	if err = pushClient.postPush(); err != nil {
		return
	}
	return
}

type (
	Config struct {
		ServerDomain string
		SoftDomain   string
		CallBackURI  string
		PackFiles    []string `comment:"push时打包的文件列表"`

		Soft
		once           sync.Once
		iniFile        *ini.File
		configFileName string
	}
	Soft struct {
		Name   string
		NameCN string
		//分支
		Branch string
		//目标软件版本号
		SoftVersion string
		BinPath     string
		//安装文件路径,将软件所有文件压缩到一个zip存到服务器;统一命名为install.zip
		FullFilePath string
		//zip文件MD5验校
		HashCode string
	}

	Push struct {
		needPush bool
		//上传之后服务器返回的路径
		serverPath string
	}
)

func (c *Config) save() (err error) {
	if err = c.iniFile.ReflectFrom(c); err != nil {
		return
	}
	err = c.iniFile.SaveTo(c.configFileName)
	return
}

func (p *Push) postPush() (err error) {
	cfg := NewConfig()
	rq := utils.NewRequester()
	rq.SetUrl(fmt.Sprintf("http://%s/push", cfg.ServerDomain))
	cfg.Soft.FullFilePath = p.serverPath
	option := rq.Option()
	option.JSON = &cfg.Soft
	rq.SetOption(option)
	rq.SetRequestFunc(func(rp *grequests.Response) (e error) {
		bindBean := new(libgin.ReturnJSON)
		if e = rp.JSON(bindBean); e != nil {
			return
		}
		message := bindBean.Message
		if !rp.Ok {
			return errors.New(message.(string))
		}
		log.Println("push完成")
		return
	})
	return rq.Request(utils.POST)
}

//上传文件
func (p *Push) postUpload() (err error) {
	cfg := NewConfig()
	rq := utils.NewRequester()
	rq.SetUrl(fmt.Sprintf("http://%s/upload?cover=true&limit_size=false", cfg.ServerDomain))
	var files = make([]grequests.FileUpload, 0)
	var fd *os.File
	fd, err = os.Open(filepath.Join(abio.SelfDir(), packName))
	if err != nil {
		return
	}
	files = append(files, grequests.FileUpload{
		FileName:     packName,
		FileContents: fd,
		FieldName:    "file",
	})
	rq.Option().Files = files
	rq.Option().Data = map[string]string{
		"root": "soft",
		"path": cfg.Name,
	}
	rq.SetRequestFunc(func(rp *grequests.Response) (e error) {
		bindBean := new(libgin.ReturnJSON)
		if e = rp.JSON(bindBean); e != nil {
			return
		}
		message := bindBean.Message
		if !rp.Ok {
			return errors.New(message.(string))
		}
		p.serverPath = message.(string)
		return
	})
	return rq.Request(utils.POST)
}

func (p *Push) checkHash() error {
	cfg := NewConfig()
	rq := utils.NewRequester()
	rq.SetUrl(fmt.Sprintf("http://%s/push?Name=%s&NameCN=%s&Branch=%s",
		cfg.ServerDomain, cfg.Name, cfg.NameCN, cfg.Branch))
	rq.SetRequestFunc(func(rp *grequests.Response) (e error) {
		bindBean := new(libgin.ReturnJSON)
		if e = rp.JSON(bindBean); e != nil {
			return
		}
		message := bindBean.Message
		if !rp.Ok {
			return errors.New(message.(string))
		}
		soft := new(Soft)
		bts, er := json.Marshal(message)
		if er != nil {
			return er
		}
		if e = json.Unmarshal(bts, soft); e != nil {
			return
		}
		p.needPush = true
		return
	})
	return rq.Request(utils.GET)
}

//打包文件,并生成hash码,->写入配置文件
func (p *Push) pack() (err error) {
	cfg := NewConfig()
	shellStr := `(cd $(dirname $(which {app_name})); zip -r {pack_name} {files})`
	shellStr = strings.Replace(shellStr, "{app_name}", common.Server, -1)
	shellStr = strings.Replace(shellStr, "{pack_name}", packName, -1)
	shellStr = strings.Replace(shellStr, "{files}", strings.Join(cfg.PackFiles, " "), -1)
	_, err = exec.Command("/bin/bash", "-c", shellStr).Output()
	if err != nil {
		return
	}
	return p.createFullFileHash()
}

func (p *Push) createFullFileHash() (err error) {
	cfg := NewConfig()
	var fileInc *os.File
	fileInc, err = os.Open(filepath.Join(abio.SelfDir(), packName))
	if err != nil {
		return
	}
	defer func() {
		_ = fileInc.Close()
	}()
	md5hash := md5.New()
	_, err = io.Copy(md5hash, fileInc)
	if err != nil {
		return
	}
	cfg.HashCode = hex.EncodeToString(md5hash.Sum([]byte{}))
	err = cfg.save()
	return
}
